#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <fcntl.h>

#include <openssl/md5.h>

#define  MD5_LEN  16 
#define  BUFF_SIZE 1024*16

#define UNDONE ".tmp"
#define CHECK_ERR "err ckeck\n"
#define FILE_EXIST "exist"

                     // unsigned char md[MD5_LEN];
void fun_md5(int fd, unsigned char* md)
{
    MD5_CTX ctx;
    MD5_Init(&ctx);

    unsigned long len = 0;
    char buff[ BUFF_SIZE ];
    while( (len = read(fd,buff,BUFF_SIZE )) > 0 )
    {
        MD5_Update(&ctx,buff,len);
    }

    MD5_Final(md,&ctx);
}

void print_md5(unsigned char md[])
{
    int i = 0;
    for( ; i < MD5_LEN; i++ )
    {
        printf("%02x",md[i]);
    }

    printf("\n");
}

void md5_to_char(unsigned char md[], char *md5)
{
    for(int i = 0; i < MD5_LEN; i++ )
    {
        sprintf(md5 + i * 2, "%02x", md[i]);
    }
}

void recv_file(int c, char* file_name, char* s)
{
    if (file_name == NULL || s == NULL)
    {
        return;
    }

    //为文件名拼接 .tmp --断点续传
    char filename[128] = {0};
    strcpy(filename, file_name);
    strcat(filename, UNDONE);

    send(c, s, strlen(s), 0);// s = "get a.txt"

    char status[128] = {0};
    if (recv(c, status, 127, 0) <= 0)
    {
        printf("ser close\n");
        return;
    }
    if (strncmp(status, "ok#", 3) != 0)
    {
        printf("download err: %s\n", status + 3);
        return;
    }

    //可通过PATH+filename 存放在用户想要的位置
    int fd = open(filename, O_WRONLY);
    int offset = 0;
    if (fd == -1)
    {
        fd = open(filename, O_WRONLY | O_CREAT, 0600);
        if (fd == -1)
        {
            send(c, "err", 3, 0);
        }
    }
    else
    {
        offset = lseek(fd, 0, SEEK_END);//获取文件大小
    }
    char send_offset[128] = {"ok#"};
    sprintf(send_offset + 3, "%d", offset);//发送段插入偏移
    send(c, send_offset, strlen(send_offset), 0);

    //获取filesize MD5
    long long filesize = atoll(status + 3);
    char md5_ser[MD5_LEN * 2 + 1] = {0};
    sscanf(status + 3, "%lld %s", &filesize, md5_ser);
    printf("object file:%s filesize:%lld md5: %s\n", filename, filesize, md5_ser);


    long long curr_file = offset;
    int num = 0;
    char data[1024] = {0};
    while (true)
    {
        num = recv(c, data, 1024, 0);
        if (num <= 0)
        {
            printf("ser close\n");
            break;
        }
        write(fd, data, num);
        curr_file += num;
        float f = curr_file * 100.0 / filesize;

        offset = curr_file * 50 / filesize;
        char bar[52];
        memset(bar, '#', sizeof(char) * offset);
        printf("\033[1;32m[%-50s]\033[0m [%.2f%%]\r", bar, f);
        fflush(stdout);

        if (curr_file >= filesize)
        {
            break;
        }
    }
    printf("%lld\n", curr_file);

    //重新打开文件 获取md5值
    close(fd);
    fd = open(filename, O_RDONLY);
    if (fd == -1)
    {
        printf(CHECK_ERR);
        return;
    }
    lseek(fd, 0, SEEK_SET);

    //获取md5值
    unsigned char md[MD5_LEN + 1] = {0};
    fun_md5(fd, md);
    print_md5(md);
    char md5_cli[MD5_LEN * 2 + 1] = {0};
    md5_to_char(md, md5_cli);
    printf("%s\n", md5_cli);
    if (strcmp(md5_cli, md5_ser) != 0)
    {
        printf(CHECK_ERR);
        return;
    }


    pid_t pid = fork();
    if (pid == -1)
    {
        exit(1);
    }

    char cmd[128] = {"mv "};
    strcat(cmd, filename);
    strcat(cmd, " ");
    strcat(cmd, file_name);
    char* myargv[10] = {0};
    char* index = NULL;
    myargv[0] = strtok_r(cmd, " ", &index);
    int i = 0;
    while (myargv[i] != 0)
    {
        myargv[++i] = strtok_r(NULL, " ", &index);
    }
    if (pid == 0)
    {
        execvp(myargv[0], myargv);
        exit(1);
    }
    printf("\nfile get success\n");
    close(fd);
}

void send_file(int c, char* filename, char* s)
{
    //up 协议    1.客户端传送数据 up filename filesize md5
    //          2. 服务器传送文件偏移值，断点续传          两次握手
    if (filename == NULL || s == NULL)
    {
        return;
    }

    int fd = open(filename, O_RDONLY);
    if (fd == -1)
    {
        return;
    }

    //获取文件大小
    long long filesize = lseek(fd, 0, SEEK_END);
    if (filesize == -1)
    {
        return;
    }

    //计算md值
    lseek(fd, 0, SEEK_SET);
    unsigned char md[MD5_LEN + 1] = {0};
    fun_md5(fd, md);
    print_md5(md);

    //up指令数据传输
    strcat(s, " ");
    sprintf(s + strlen(s), "%lld", filesize);
    strcat(s, " ");
    md5_to_char(md, s + strlen(s));
    printf("len : %ld  %s\n", strlen(s), s);
    send(c, s, strlen(s), 0);// s = "up a.txt size md5"

    //获取服务器应答
    char status[128] = {0};
    if (recv(c, status, 127, 0) <= 0)
    {
        printf("ser close\n");
        return;
    }
    if (strncmp(status, FILE_EXIST, strlen(FILE_EXIST)) == 0)
    {
        printf("\nfile up success\n");
        close(fd);
        return;
    }
    if (strncmp(status, "ok#", 3) != 0)
    {
        printf("download err: %s\n", status + 3);
        return;
    }

    //获取文件偏移，修改文件指针，断点续传
    int offset = atoi(status + 3);//断点续传，获取文件偏移
    lseek(fd, offset, SEEK_SET);

    char data[1024] = {0};
    int num = 0;
    long long curr_file = offset;
    while ((num = read(fd, data, 1024)) > 0)
    {
        curr_file += num;
        if (send(c, data, num, 0) <= 0)
        {
            break;
        }
        float f = curr_file * 100.0 / filesize;

        printf("%.2f\r", f);
        fflush(stdout);

        //offset = curr_file * 50 / filesize;
        //char bar[52];
        //memset(bar, '#', sizeof(char) * offset);
        //printf("\033[1;32m[%-50s]\033[0m [%.2f%%]\r", bar, f);
        //fflush(stdout);
    }
    printf("%lld\n", curr_file);

    //文件校验，服务器是否获得相同文件
    memset(status, 0, sizeof(status));
    if (recv(c, status, 127, 0) <= 0)
    {
        printf("ser close\n");
        return;
    }
    if (strncmp(status, "ok#", 3) != 0)
    {
        printf("%s\n", status);
        close(fd);
        return;
    }
        
    printf("\nfile up success\n");
    close(fd);
}

int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        printf("sock err\n");
        exit(1);
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (res == -1)
    {
        printf("connect err\n");
        exit(1);
    }

    while (true)
    {
        printf("connect>>");
        fflush(stdout);

        char buff[128] = {0};
        fgets(buff, 128, stdin);
        buff[strlen(buff) - 1] = 0;

        char send_buff[128] = {0};
        strcpy(send_buff, buff);

        char* myargv[10] = {0};
        int i = 0;
        char* s = strtok(buff, " ");
        while (s != NULL)
        {
            myargv[i++] = s;
            s = strtok(NULL, " ");
        }

        char* cmd = myargv[0];

        if (cmd == NULL)
        {
            continue;
        }
        else if (strcmp(cmd, "exit") == 0)
        {
            break;
        }
        else if (strcmp(cmd, "get") == 0)
        {
            recv_file(sockfd, myargv[1], send_buff);
        }
        else if (strcmp(cmd, "up") == 0)
        {
            send_file(sockfd, myargv[1], send_buff);
        }
        else
        {
            send(sockfd, send_buff, strlen(send_buff), 0);
            char read_buff[4096] = {0};
            int num = recv(sockfd, read_buff, 4095, 0);
            if (num <= 0)
            {
                printf("服务器异常关闭\n");
                continue;
            }
            if (strncmp(read_buff, "ok#", 3) == 0)
            {
                printf("%s\n", read_buff + 3);
            }
            else
            {
                printf("failed : %s\n", read_buff);
            }
        }
    }
    close(sockfd);
}
